// 泛型

// 泛型可以理解为是泛指的类型,软件工程中,我们不仅要创建一致的定义良好的API
// 同时也要考虑可重用性,组件不仅能够支持当前的数据类型
// 这在创建大型系统时为你提供了十分灵活的功能

// 创建第一个使用泛型的例子: indentity函数
// 这个函数会返回任何传入它的值

function indentity0(arg: any): any {
    return arg;
}
// 使用any类型会导致这个函数可以接收任何类型的arg参数,
// 这样就丢失了一些信息: 传入的类型与返回的类型应该是相同的
// 但是如果加上这个判断
// 代码又会变得格外臃肿
function indentity(arg: number): number;
function indentity(arg: string): string;
function indentity(arg: object): object;
function indentity(arg: any): any {
    return arg;
}


// 基于泛型
// 基于上述,我们1需要一种方法使返回值的类型与传入参数的类型是相同的,
// 这里,我们使用了类型变量,它是一种特殊的变量,只是用于表示类型而不是值

// 我们给indentity添加了类型变量T,
// T帮助我们捕获用户传入的类型(比如:number)
// 之后我们就再次使用了T当作返回值类型,
// 现在我们可以自动参数类型与返回值类型是相同的了
// 和允许我们跟踪函数你是要的类型信息

// 我们把这个版本的indentity函数叫做泛型,
// 因此它可以适用于多个类型
// 不同于使用any , 他不会丢失信息
function indentity2<T>(arg: T): T {
    return arg;
}


// 泛型的常见用法

// 我们定义了泛型函数后,可以用两种方法使用
// 这里我们明确的制定了T是string类型,并作为一个参数传给函数,使用了<>括起来而不是()
// 第一种是, 传入所有的参数, 包含类型参数:
let output = indentity2<string>("myString");

// type of output will be 'string'
// 第二种方法更加普遍.利用了类型推论 -- 
// 即编译器会根据传入的参数自动地帮助我么确定T的类型:
let output2 = indentity2("myString");

// 注意我么没必要使用 <> 来明确地传入类型;
// 编译器可以查看myString的值, 然后把T设置为它的类型, 
// 类型推论帮助我们保持代码精简和高可读性
// 如果编译器不能够自动给的推断出类型的话
// 只能像上面那样明确的传入T的类型,
// 在一些复杂的情况下, 这是可能出现的

// 泛型的使用
// 使用泛型变量
// 使用泛型创建像inedentity这样的泛型函数时,
// 编译器要求你在函数体必须正确使用这个同用的类型
// 换句话说,你必须把这些参数当作时任意或所有类型

function loggingIndentity<T>(arg: T): T {
    // 这里就会直接报错,原因是因为不是每一种数据类型都有length属性的
    // console.log(arg);
    return arg;
}
function loggingIndentity2<T>(arg: Array<T>): Array<T> {
    // console.log(arg.length);
    return arg;
}
function loggingIndentity3<array>(arg: Array<string>): Array<string> {
    // console.log(arg.length);
    return arg;
}
loggingIndentity(["1", 1]);
// 你可以这样理解loggingIdentity的类型:
// 泛型函数loggingIdentity, 接口函数参数T和参数arg,
// arg是个数组项目类型时T的数组,并返回元素类型是T的数组
// 并返回园路类型是T的数组
// 这可以让我们把泛型变量T当作类型的一部分使用
// 而不是整个类型,增加了灵活性


// 使用泛型函数和泛型接口
// 泛型函数的类型与非泛型函数的类型没什么不同,
// 只是有一个类型参数在最前面,
// 像函数声明一样:
let myIndentity: <U>(arg: U) => U = function indentity<T>(arg: T): T {
    return arg;
}
function fn<T>(x: Array<T>): T {
    // console.log(x.length);
    return x[0];
}
fn([1]);


// 泛型接口的设计与其他类似
interface GenericIdentityFn {
    <T>(arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIndentity2: GenericIdentityFn = indentity;

// 我们可能把泛型参数当作整个接口的一个参数
// 这样我们就能清楚的知道使用的具体是哪个泛型类型
// (比如: Dictionary<string>而不只是Dictionary)
// 这样接口里的其他成员也嗯知道这个参数的类型了
interface GenericIdentityFn2<T> {
    // 把泛型定义放到接口的后面
    // 这样我们在调用这个就扣是就可以传入某个具体的类型来细致化接口
    (arg: T): T;
}
function identity2<T>(arg: T): T {
    return arg;
}
let myIdentiy: GenericIdentityFn2<number> = identity2;


// 泛型类Class

// 泛型类看上去与泛型接口差不多,
// 泛型类十一(<>)括起来泛型类型, 跟在类名后面
class GenericNumber<T>{
    zeroValue: T;//改变量的值只能是T类型的
    add: (x: T, y: T) => T;//改函数传入的参数的类型和返回值的类型都是T类型的
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) { return x + y };
myGenericNumber.add(1,2);//没问题
console.log(myGenericNumber);
// myGenericNumber.add("1",2);//报错,因为该函数只能传入数组类型的参数


// 类有两部分:静态和实例部分
// 泛型类指的是实例部分的类型,
// 所以内的静态属性不能使用这个泛型类型
// 静态类型的一旦定义, 就已经成形了


// 在typescript使用泛型创建工厂函数时, 需要引用构造函数的类型
function create<T>(c:{new():T;}):T{
    // 该函数可传入一个, 新的参数
    // 新的函数用过new关键字返回的数据类型得是符合T的类型
    return new c();
}

// 泛型约束
// 前面有一个例子,要求实现一个传入什么数据就返回什么数据的函数
// 一个个的定义每种输入和输出太过麻烦
// 直接使用泛型, 这个有没法细致化的定义输入的要求
function loggingIndentity4<T>(arg:T):T{
    // console.log(arg.length);
    return arg;
}
// 为此我们定义一个接口来描述约束条件
// 约束条件就是一个接口,
// 我们使用这个接口和extends关键字来实现约束:
interface Lengthwise{
    // 定义了一个接口, 该接口表示只是的有一个Length的属性,且值为数字
    length:number;
}
// 泛型T拓展自Lengthwise接口,使得泛型T多了一个约束条件,
// 那就是必须得是一个用户length属性的数据
function loggingIndentity5<T extends Lengthwise>(arg:T):T{
    console.log(arg.length);
    return arg;
}
// 现在这个泛型函数被定义了约束,因此它不再是适用于任意类型
